﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ComputerArchitecture.Core;

namespace ComputerArchitecture.Numbers
{
    public class FloatPointNumber : IFloatPointNumber
    {
        #region Fields

        private readonly IUnsignedBitNumber _exponent;
        private readonly IUnsignedBitNumber _mantissa;
        private readonly Bit _signBit;

        #endregion

        #region Properties

        public IUnsignedBitNumber Exponent
        {
            get { return _exponent; }
        }

        public IUnsignedBitNumber Mantissa
        {
            get { return _mantissa; }
        }
    
        public int Size
        {
            get { return 1 + _mantissa.Size + _exponent.Size; }
        }

        public Bit SignBit
        {
            get { return _signBit; }
        }

        public bool IsNaN
        {
            get { return IsSignalingNaN || IsQuietNaN; }
        }

        public bool IsSignalingNaN 
        { 
            get { return Exponent.All(bit => bit.IsSet) && !Mantissa[0].IsSet && Mantissa.Skip(1).Any(bit => bit.IsSet); }
        }

        public bool IsQuietNaN
        {
            get { return Exponent.All(bit => bit.IsSet) && Mantissa[0].IsSet; }
        }

        public bool IsPositiveInfinity
        {
            get { return !SignBit.IsSet && IsInfinity; }
        }

        public bool IsNegativeInfinity
        {
            get { return SignBit.IsSet && IsInfinity; }
        }

        public bool IsInfinity
        {
            get { return Exponent.All(bit => bit.IsSet) && Mantissa.All(bit => !bit.IsSet); }
        }

        public bool IsPositiveZero 
        {   
            get { return !SignBit.IsSet && IsZero; }
        }

        public bool IsNegativeZero 
        { 
            get { return SignBit.IsSet & IsZero; } 
        }

        public bool IsZero 
        { 
            get
            {
                return Exponent.All(bit => !bit.IsSet) && Mantissa.All(bit => !bit.IsSet);
            }
        }

        public bool IsNormalized 
        {
            get { return Exponent.Any(bit => bit.IsSet); }
        }
        
        public bool IsUnnormalized
        {
            get { return Exponent.All(bit => !bit.IsSet) && Mantissa.Any(bit => bit.IsSet); }
        }

        
        #endregion

        #region Constructors

        public FloatPointNumber(Bit signBitBit, IUnsignedBitNumber exponent, IUnsignedBitNumber mantissa)
        {
            ArgumentValidator.NullValidate(signBitBit);
            _signBit = signBitBit;
            ArgumentValidator.NullValidate(exponent);
            _exponent = exponent;
            ArgumentValidator.NullValidate(mantissa);
            _mantissa = mantissa;
        }

        public FloatPointNumber(Bit signBitBit, int exponentSize, int mantissaSize)
        {
            ArgumentValidator.NullValidate(signBitBit);
            _signBit = signBitBit;
            _exponent = new UnsignedBitNumber(exponentSize);
            _mantissa = new UnsignedBitNumber(mantissaSize);
        }

        #endregion

        #region Methods

        public object Clone()
        {
            return new FloatPointNumber((Bit) _signBit.Clone(), (IUnsignedBitNumber) Exponent.Clone(),
                                        (IUnsignedBitNumber) Mantissa.Clone());
        }


        public IUnsignedBitNumber GetImplicitMantissa()
        {
            IUnsignedBitNumber newMantissa = new UnsignedBitNumber(Mantissa.Size + 1);
            newMantissa[0].Set();
            Mantissa.CopyTo(newMantissa, 1, 0, Mantissa.Size);
            return newMantissa;
        }


        public bool Equals(IFloatPointNumber other)
        {
            if (ReferenceEquals(other, null))
            {
                return ReferenceEquals(this, null);
            }
            return SignBit.Equals(other.SignBit) && Exponent.Equals(other.Exponent) && Mantissa.Equals(other.Mantissa);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(obj, null))
            {
                return ReferenceEquals(this, null);
            }
            IFloatPointNumber floatPointNumber = obj as IFloatPointNumber;
            return Equals(floatPointNumber);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int hashCode = (_exponent != null ? _exponent.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (_mantissa != null ? _mantissa.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (_signBit != null ? _signBit.GetHashCode() : 0);
                return hashCode;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public static bool operator ==(FloatPointNumber left, FloatPointNumber right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(FloatPointNumber left, FloatPointNumber right)
        {
            return !Equals(left, right);
        }

        public IEnumerator<Bit> GetEnumerator()
        {
            yield return SignBit;
            foreach (var bit in Exponent)
            {
                yield return bit;
            }
            foreach (var bit in Mantissa)
            {
                yield return bit;
            }
        }

        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder(Size + 2);
            stringBuilder.Append(_signBit.ToString());
            stringBuilder.Append(' ');
            stringBuilder.Append(_exponent);
            stringBuilder.Append(' ');
            stringBuilder.Append(_mantissa);
            return stringBuilder.ToString();
        }

        private static FloatPointNumber GetInfinity(Bit signBit, int exponentSize, int mantissaSize)
        {
            FloatPointNumber value = new FloatPointNumber(signBit, exponentSize, mantissaSize);
            for (int i = 0; i < value.Exponent.Size; ++i)
            {
                value.Exponent[i].Set();
            }
            return value;
        }

        public static FloatPointNumber GetPositiveInfinity(int exponentSize, int mantissaSize)
        {
            return GetInfinity(Bit.Zero, exponentSize, mantissaSize);
        }

        public static FloatPointNumber GetNegativeInfinity(int exponentSize, int mantissaSize)
        {
            return GetInfinity(Bit.One, exponentSize, mantissaSize);
        }

        public static FloatPointNumber GetPositiveZero(int exponentSize, int mantissaSize)
        {
            return new FloatPointNumber(Bit.Zero, exponentSize, mantissaSize);
        }

        public static FloatPointNumber GetNegativeZero(int exponentSize, int mantissaSize)
        {
            return new FloatPointNumber(Bit.One, exponentSize, mantissaSize);
        }

        public static FloatPointNumber GetQuietNaN(int exponentSize, int mantissaSize)
        {
            FloatPointNumber number = new FloatPointNumber(Bit.Zero, exponentSize, mantissaSize);
            for (int i = 0; i < exponentSize;++i)
            {
                number.Exponent[i].Set();
            }
            number.Mantissa[0].Set();
            return number;
        }

        #endregion
    }
}
